﻿using System;
using System.IO;
using System.IO.Packaging;
using System.Xml;

namespace Microsoft.Office.PowerPoint.STAMP.Core
{
    /// <summary>
    /// OpenXML helper class to pull out the media objects.
    /// </summary>
    public static class OpenXMLHelper
    {
        /// <summary>
        /// Saves the Media object to an temp directory for playback
        /// </summary>
        /// <param name="fileName">Presentation file name.</param>
        /// <param name="mediaName">Media name used to identify the object.</param>
        /// <param name="slideId">Slide id used to find the media object.</param>
        /// <param name="tempTargetPath">Path where previous media object was saved to.</param>
        /// <returns>Returns path where the media object is saved.</returns>
        public static string GetMediaSavedLocation(string fileName, string mediaName, string slideId, string tempTargetPath)
        {
            //  Given a slide deck name and a slide title within the deck,
            //  replace the first image on the slide with the supplied image.
            //  If the slide does not have an image on it, do nothing.

            const string documentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
            const string slideRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";

            const string audioRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
            const string videoRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";

            const string presentationmlNamespace = "http://schemas.openxmlformats.org/presentationml/2006/main";
            const string drawingmlNamespace = "http://schemas.openxmlformats.org/drawingml/2006/main";
            const string relationshipNamespace = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";

            PackagePart documentPart = null;
            string mediaTempLocation = String.Empty;

            using (Package pptPackage = Package.Open(fileName, FileMode.Open, FileAccess.ReadWrite))
            {
                //  Get the main document part (presentation.xml).
                foreach (
                    PackageRelationship documentRelationship in
                        pptPackage.GetRelationshipsByType(documentRelationshipType))
                {
                    Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative),
                                                                   documentRelationship.TargetUri);
                    documentPart = pptPackage.GetPart(documentUri);
                    //  There is only one document.
                    break;
                }

                //  Manage namespaces to perform Xml XPath queries.
                NameTable nt = new NameTable();
                XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
                nsManager.AddNamespace("p", presentationmlNamespace);
                nsManager.AddNamespace("a", drawingmlNamespace);
                nsManager.AddNamespace("r", relationshipNamespace);

                PackagePart slidePart = null;
                Uri slideUri = null;

                //  Select each slide document part (slides/slideX.xml)
                //  using the relationship from the officeDocument part.
                if (documentPart != null)
                {
                    foreach (
                        PackageRelationship slidePartRelation in
                            documentPart.GetRelationshipsByType(slideRelationshipType))
                    {
                        slideUri = PackUriHelper.ResolvePartUri(documentPart.Uri, slidePartRelation.TargetUri);
                        slidePart = pptPackage.GetPart(slideUri);

                        string slideName =
                            slideUri.OriginalString.Substring(0, slideUri.OriginalString.LastIndexOf(".")).Replace(
                                @"/ppt/slides/", "");

                        if (slideName.Equals(slideId))
                        {
                            //  Get the slide part from the package.
                            XmlDocument slideDoc = new XmlDocument(nt);
                            slideDoc.Load(slidePart.GetStream());

                            XmlNodeList picNodeList = slideDoc.SelectNodes("//p:pic", nsManager);

                            if (picNodeList != null)
                            {
                                foreach (XmlNode picNode in picNodeList)
                                {
                                    string searchName = string.Format("p:nvPicPr//p:cNvPr[@name='{0}']", mediaName);
                                    XmlNode nvPicPrNode = picNode.SelectSingleNode(searchName, nsManager);

                                    if (nvPicPrNode != null)
                                    {
                                        XmlNode videoNode = picNode.SelectSingleNode("p:nvPicPr//p:nvPr//a:videoFile",
                                                                                     nsManager);
                                        XmlNode audioNode = picNode.SelectSingleNode("p:nvPicPr//p:nvPr//a:audioFile",
                                                                                     nsManager);

                                        if (videoNode != null)
                                        {
                                            if (videoNode.Attributes != null)
                                            {
                                                mediaTempLocation = MediaFileLocation(pptPackage, slidePart,
                                                                                      videoRelationshipType,
                                                                                      videoNode.Attributes["r:link"].
                                                                                          Value,
                                                                                      mediaName, tempTargetPath);
                                            }
                                        }

                                        if (audioNode != null)
                                        {
                                            if (audioNode.Attributes != null)
                                            {
                                                mediaTempLocation = MediaFileLocation(pptPackage, slidePart,
                                                                                      audioRelationshipType,
                                                                                      audioNode.Attributes["r:link"].
                                                                                          Value,
                                                                                      mediaName, tempTargetPath);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return mediaTempLocation;
        }

        /// <summary>
        /// Gets a temp filename from the system.
        /// </summary>
        /// <returns>Returns the file name.</returns>
        private static string GetTempFileName()
        {
            string filename = Path.GetTempFileName();

            File.Delete(filename);

            return Path.GetFileNameWithoutExtension(filename);
        }

        /// <summary>
        /// Gets the directory and file name
        /// </summary>
        /// <param name="mediaName">Media name to add to the file name.</param>
        /// <returns>Returns the directory and file name.</returns>
        private static string GetDirFileName(string mediaName)
        {
            string target = Path.Combine(Path.GetTempPath(), GetTempFileName() + "_" + mediaName);
            return target;
        }

        /// <summary>
        /// Gets the Media file out the package and saves it to the temp location
        /// </summary>
        /// <param name="pptPackage">Presentation package in which the media file is located.</param>
        /// <param name="slidePart">Slide part that contains the media file.</param>
        /// <param name="relationshipType">Relationship type audio or video to find the correct media file.</param>
        /// <param name="rId">Id used in finding the right media file.</param>
        /// <param name="mediaName">Media name used in creating the file name.</param>
        /// <param name="tempTargetPath">Path where previous media object was saved to.</param>
        /// <returns>Returns path where the media file is saved.</returns>
        private static string MediaFileLocation(Package pptPackage, PackagePart slidePart, string relationshipType, string rId, string mediaName, string tempTargetPath)
        {
            PackagePart mediaPart = null;
            string targetPath = String.Empty;
            //  Look through the slide's relationships to see if there is an audio.
            foreach (PackageRelationship mediaRelationship in slidePart.GetRelationshipsByType(relationshipType))
            {
                if (rId == mediaRelationship.Id)
                {
                    Uri mediauUri = PackUriHelper.ResolvePartUri(slidePart.Uri, mediaRelationship.TargetUri);
                    mediaPart = pptPackage.GetPart(mediauUri);

                    if (mediaPart != null)
                    {
                        if (!String.IsNullOrEmpty(tempTargetPath) && File.Exists(tempTargetPath) &&
                            tempTargetPath.Contains(mediaName))
                        {
                            return tempTargetPath;
                        }

                        targetPath = GetDirFileName(mediaName);

                        using (Stream source = mediaPart.GetStream())
                        {
                            using (Stream destination = File.OpenWrite(targetPath))
                            {
                                byte[] buffer = new byte[0x1000];
                                int read;
                                while ((read = source.Read(buffer, 0, buffer.Length)) > 0)
                                {
                                    destination.Write(buffer, 0, read);
                                }
                            }
                        }
                    }
                }
            }
            return targetPath;
        }
    }
}